import os
import json
import random
import string
import requests
import base64
import time
from urllib import parse

from Crypto import Random
from Crypto.Hash import SHA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
from Crypto.PublicKey import RSA

from flask import current_app

github_appid = '9051298fc566ede36c3c'
with open('github_appsecret', 'r') as f:
  github_appsecret = f.read().strip()

class Github:
    @staticmethod
    def Login(code):
        login_url = f'https://github.com/login/oauth/access_token'
        access_token = requests.post(login_url, {
            'client_id': github_appid,
            'client_secret': github_appsecret,
            'code': code,
        }).text
        userdata_url = f'https://api.github.com/user?{access_token}'
        res = requests.get(userdata_url).text
        return json.loads(res)

class ServerCipher:
    def __init__(self):
        self._cipher = None
        self._decipher = None

        path_key_pub = os.path.abspath(os.path.join(os.path.expanduser('~'),'.ssh/id_rsa.pub'))
        path_key_private = os.path.abspath(os.path.join(os.path.expanduser('~'),'.ssh/id_rsa'))

        with open(path_key_pub, 'r')  as f:
            key_pub = f.read()
            key_pub = RSA.importKey(key_pub)
            self._cipher = Cipher_pkcs1_v1_5.new(key_pub)
            
        with open(path_key_private, 'r')  as f:
            key_private = f.read()
            key_private = RSA.importKey(key_private)
            self._decipher = Cipher_pkcs1_v1_5.new(key_private)

        self.random_generator = Random.new().read
    
    def cipher(self, msg):
        return base64.b64encode(self._cipher.encrypt(msg.encode('utf-8'))).decode('utf-8')

    def decipher(self, msg):
        return self._decipher.decrypt(base64.b64decode(msg.encode('utf-8')), self.random_generator).decode('utf-8')

    def test(self):
        print('--- cipher test ---')
        msg = 'this is origin message.'
        cipher_text = self.cipher(msg)
        text = self.decipher(cipher_text)
        if type(cipher_text) != str:
            return False
        if type(text) != str:
            return False
        if msg != text:
            return False
        return True

class FileMapItem:
    def __init__(self, path, expire):
        self.path = path
        self.expire = expire

class FileMap:
    
    def __init__(self):
        self._map = {}

    def add(self, path, dur:float=900):
        if not os.path.exists(path):
            current_app.logger.error('[keen][FileMap][add]', 'path doesnot exist!', path)
            return
        prefix = ''.join(random.sample(string.ascii_letters + string.digits, 8))
        self._map[prefix] = FileMapItem(path, time.time() + dur)

        return prefix
    
    def get(self, prefix):
        item = self._map.get(prefix)
        if item is None:
            return None
        if item.expire < time.time():
            del self._map[prefix]
            return None
        else:
            return item.path

    def test(self):
        res = []
        for key,value in self._map.items():
            res.append({
                "key": key,
                "value": value.path
            })
        return str(res)

class CmdEncoder:
    Head_Limit = 64

    @staticmethod
    def head_encode(s):
        full_s = s.ljust(CmdEncoder.Head_Limit, ' ')
        return full_s.encode()
    
    @staticmethod
    def head_decode(b):
        b_len = 0
        for i in range(CmdEncoder.Head_Limit):
            if b[i] == 0:
                b_len = i
                break
        return b[0:b_len].decode()

class FileServer:

    CmdList = ['ls', 'cat', 'cd', 'keen']

    root_path = ''


    def __init__(self):
        

        self.data_root = os.path.abspath(os.path.join(FileServer.root_path, '.keen'))

        self.work_dir = FileServer.root_path

        self.cipher = ServerCipher()

        if not os.path.exists(FileServer.root_path):
            print('path ' + FileServer.root_path + ' does not exist')
            exit()
        

        if not os.path.exists(self.data_root):
            os.mkdir(self.data_root)
            print ('[create data_root]' + self.data_root)

        print(self.data_root)

    def ls(self) -> str:
        res = []
        file_name_list = os.listdir(self.work_dir)
        for name in file_name_list:
            full_path = os.path.join(self.work_dir, name)
            res.append({
                'name': name,
                'is_dir': os.path.isdir(full_path),
                'time': max(os.path.getctime(full_path), os.path.getmtime(full_path)),
                'size': os.path.getsize(full_path),
            })
        if self.work_dir == FileServer.root_path:
            wd = '/'
        else:
            wd = self.work_dir[len(FileServer.root_path):].replace('\\', '/')
        return json.dumps({
            'path': wd,
            'file': res
        })

    def get_full_path(self, arg):
        if arg[0] == '/':
            target = os.path.join(FileServer.root_path, arg[1:])
        else:
            target = os.path.join(self.work_dir, arg)

        return os.path.abspath(target)
        

    def path_invalid(self, arg):
        is_valid = arg.startswith(FileServer.root_path)
        if not is_valid:
            print('[keen][FileServer][path_invalid]', 'path is not valid', arg, FileServer.root_path)
        return is_valid

    def cd(self, arg) -> None:
        
        full_path = self.get_full_path(arg)

        if not os.path.isdir(full_path):
            print('[keen][FileServer][cd]', 'path is not dir')
            return

        if not self.path_invalid(full_path):
            return

        self.work_dir = full_path



    def get(self, arg) -> bytes:

        full_path = self.get_full_path(arg)

        if not os.path.isfile(full_path):
            return

        f = open(full_path, 'rb')
        data=f.read()
        f.close()

        return data

    def get_url(self, arg) -> str:
        
        # arg is quoted
        uri = arg

        # quote full_path
        full_path = self.get_full_path(parse.unquote(uri))
        # full_path_quoted = parse.quote(full_path)

        print('fp --- ', full_path)

        if not os.path.isfile(full_path):
            return

        # url = 'http://127.0.0.1:30002/?' + prefix + '/' + uri.split('/')[-1] + "?" + full_path_quoted
        url = 'http://127.0.0.1:30002/r-add/' + self.cipher.cipher(full_path)

        # print('uriopen-- ', url)
        prefix = requests.get(url).text

        return 'r/' + prefix + '/' + uri.split('/')[-1]

    def put(self, arg, content: bytes) -> None:

        full_path = self.get_full_path(arg)
        

        if os.path.exists(full_path):
            return

        f = open(full_path, 'wb')
        f.write(content)
        f.close()

    def exec_shell(self, cmd):
        
        argv = cmd.split()
        if argv[0] not in FileServer.CmdList:
            return 'command \'' + cmd + '\' is not permit currently.'

        out_path = os.path.join(self.data_root, 'out')
        full_cmd = '' + cmd + ' > ' + out_path
        os.system(full_cmd)

        out = open(out_path, 'r')
        out_str = out.read()
        out.close()

        return out_str
        

    def exec_bytes(self, cmd):
        cmd_head_bin = cmd[0:CmdEncoder.Head_Limit]
        cmd_head = CmdEncoder.head_decode(cmd_head_bin)

        res = None
        if cmd_head == 'ls':
            res = self.ls().encode()
        elif cmd_head == 'cd':
            path = cmd[CmdEncoder.Head_Limit:].decode()
            self.cd(path)
            res = self.ls().encode()
        elif cmd_head == 'get':
            name = cmd[CmdEncoder.Head_Limit:].decode()
            res = self.get(name)
        elif cmd_head == 'get_url':
            name = cmd[CmdEncoder.Head_Limit:].decode()
            # print('name -- ', name)
            url = self.get_url(name)
            # print('url -- ', url)
            res = url.encode()
        elif cmd_head == 'put':
            name = CmdEncoder.head_decode(cmd[CmdEncoder.Head_Limit:CmdEncoder.Head_Limit * 2])
            content = cmd[CmdEncoder.Head_Limit * 2:]

            self.put(name, content)
            res = self.ls().encode()
        else:
            return 'unknow bytes command \'' + cmd_head + '\' '

        return cmd_head_bin + res

    def exec(self, cmd):
        if type(cmd) is bytes:
            # print('[byte cmd]')
            return self.exec_bytes(cmd)
        else:
            # print('[shell] ', cmd)
            return self.exec_shell(cmd)
